#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RS485通信上位机
基于PyQt5实现，使用xprotocol协议
"""

import sys
import time
import serial
import serial.tools.list_ports
import binascii
import struct
import os
import hashlib
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                           QHBoxLayout, QLabel, QComboBox, QPushButton,
                           QTextEdit, QGroupBox, QGridLayout, QSpinBox,
                           QLineEdit, QCheckBox, QMessageBox, QFormLayout,
                           QFrame, QSplitter, QProgressBar, QToolButton,
                           QLCDNumber, QGraphicsDropShadowEffect, QTabWidget,
                           QStyleFactory, QDialog, QMenu, QAction, QSizePolicy,
                           QFileDialog, QProgressDialog)
from PyQt5.QtCore import (QTimer, Qt, pyqtSignal, QThread, QPropertyAnimation, 
                         QRect, QSize, QEasingCurve, QPoint, QByteArray, 
                         QParallelAnimationGroup, QSequentialAnimationGroup,
                         QObject)
from PyQt5.QtGui import (QFont, QColor, QPalette, QIcon, QTextCursor, 
                        QValidator, QPixmap, QLinearGradient, QPainter, 
                        QBrush, QPen, QRadialGradient, QFontDatabase)

# 导入自定义协议模块
from xprotocol_parser import XProtocol

# 全局变量 控制串口线程是否读取数据，下载时为False，读取时为True
is_serial_thread_recv_flag=True

# 添加全局常量
CYBER_BLUE = "#00AEFF"
CYBER_GREEN = "#00FF9F"
CYBER_PINK = "#FF3C78"
CYBER_YELLOW = "#FFD600"
CYBER_PURPLE = "#9D00FF"
BG_DARK = "#121212"
BG_DARKER = "#0A0A0A"
BG_PANEL = "#1E1E1E"
BG_HIGHLIGHT = "#2D2D2D"

# 固件下载相关常量
FIRMWARE_PACKET_SIZE = 128  # 每包128字节
FIRMWARE_SAVE_THRESHOLD = 2048  # 每2KB保存一次
FIRMWARE_CMD_START = 0x10  # 开始下载命令
FIRMWARE_CMD_DATA = 0x11   # 数据包命令
FIRMWARE_CMD_END = 0x12    # 结束下载命令

# 16进制输入验证器
class HexValidator(QValidator):
    def validate(self, input_str, pos):
        # 允许空字符串
        if not input_str:
            return QValidator.Acceptable, input_str, pos
            
        # 检查是否是有效的16进制字符串
        try:
            # 去除前缀0x，如果有的话
            if input_str.lower().startswith('0x'):
                hex_value = input_str[2:]
            else:
                hex_value = input_str
                
            # 尝试转换为16进制值
            int(hex_value, 16)
            return QValidator.Acceptable, input_str, pos
        except ValueError:
            return QValidator.Invalid, input_str, pos

# 霓虹灯效果标签
class NeonLabel(QLabel):
    def __init__(self, text="", parent=None, color=CYBER_BLUE):
        super().__init__(text, parent)
        self.setStyleSheet(f"""
            font-family: 'Orbitron', 'Arial', sans-serif;
            font-weight: bold;
            font-size: 14px;
            color: {color};
        """)
        
        # 添加发光效果
        glow = QGraphicsDropShadowEffect()
        glow.setBlurRadius(10)
        glow.setColor(QColor(color))
        glow.setOffset(0, 0)
        self.setGraphicsEffect(glow)
        
        # 设置动画
        self.opacity_animation = QPropertyAnimation(self, b"opacity")
        self.opacity_animation.setDuration(1500)
        self.opacity_animation.setStartValue(0.6)
        self.opacity_animation.setEndValue(1.0)
        self.opacity_animation.setLoopCount(-1)
        self.opacity_animation.start()
    
    def setOpacity(self, opacity):
        effect = self.graphicsEffect()
        effect.setStrength(opacity * 2)
        
    def opacity(self):
        return self.graphicsEffect().strength() / 2

# 高科技按钮
class TechButton(QPushButton):
    def __init__(self, text, parent=None, accent_color=CYBER_BLUE):
        super().__init__(text, parent)
        self.accent_color = accent_color
        self.setupUI()
        
    def setupUI(self):
        # 设置样式
        self.setStyleSheet(f"""
            QPushButton {{
                background-color: {BG_HIGHLIGHT};
                color: white;
                border: 1px solid {self.accent_color};
                border-radius: 4px;
                padding: 6px 12px;
                font-weight: bold;
                font-family: 'Orbitron', 'Arial', sans-serif;
            }}
            QPushButton:hover {{
                background-color: {self.accent_color};
                color: black;
            }}
            QPushButton:pressed {{
                background-color: darker({self.accent_color}, 120%);
            }}
        """)
        
        # 添加发光效果
        glow = QGraphicsDropShadowEffect()
        glow.setBlurRadius(15)
        glow.setColor(QColor(self.accent_color))
        glow.setOffset(0, 0)
        self.setGraphicsEffect(glow)

# 数据流可视化组件
class DataFlowVisualizer(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedHeight(30)
        self.setStyleSheet(f"background-color: {BG_DARKER}; border-radius: 5px;")
        
        self.particles = []
        self.max_particles = 20
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_animation)
        self.timer.start(50)
        self.direction = "none"  # "send", "receive", "none"
        
    def set_direction(self, direction):
        self.direction = direction
        if direction != "none":
            # 添加新粒子
            self.add_particles(5)
            
    def add_particles(self, count):
        for _ in range(count):
            if len(self.particles) >= self.max_particles:
                break
                
            particle = {
                "pos": 0 if self.direction == "send" else self.width(),
                "speed": (5 + 10 * (random.random())) * (1 if self.direction == "send" else -1),
                "size": 3 + 5 * random.random(),
                "color": QColor(CYBER_GREEN if self.direction == "receive" else CYBER_BLUE)
            }
            self.particles.append(particle)
            
    def update_animation(self):
        if not self.particles:
            return
            
        # 更新粒子位置
        for particle in self.particles[:]:
            particle["pos"] += particle["speed"]
            
            # 如果粒子离开可视区域，从列表中移除
            if (self.direction == "send" and particle["pos"] > self.width()) or \
               (self.direction == "receive" and particle["pos"] < 0):
                self.particles.remove(particle)
                
        # 刷新绘制
        self.update()
        
    def paintEvent(self, event):
        super().paintEvent(event)
        
        if not self.particles:
            return
            
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制粒子
        for particle in self.particles:
            painter.setBrush(QBrush(particle["color"]))
            painter.setPen(Qt.NoPen)
            painter.drawEllipse(
                QPoint(int(particle["pos"]), self.height() // 2),
                int(particle["size"]),
                int(particle["size"])
            )

# 自定义进度条
class CyberProgressBar(QProgressBar):
    def __init__(self, parent=None, color=CYBER_BLUE):
        super().__init__(parent)
        self.color = color
        self.setValue(0)
        self.setTextVisible(False)
        self.setStyleSheet(f"""
            QProgressBar {{
                border: 1px solid {self.color};
                border-radius: 3px;
                background-color: {BG_DARKER};
                text-align: center;
            }}
            
            QProgressBar::chunk {{
                background-color: {self.color};
                border-radius: 2px;
            }}
        """)

# 带闪烁效果的进度条
class BlinkingProgressBar(QProgressBar):
    """带有闪烁效果的进度条，用于指示活动状态"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setValue(0)
        self.setMaximum(100)
        self.setTextVisible(False)
        self.setFixedHeight(5)
        
        # 设置动画
        self.animation = QPropertyAnimation(self, b"value")
        self.animation.setDuration(800)  # 800毫秒周期
        self.animation.setStartValue(0)
        self.animation.setEndValue(100)
        self.animation.setLoopCount(-1)  # 无限循环
        
        # 设置定时器用于颜色变化
        self.color_timer = QTimer(self)
        self.color_timer.timeout.connect(self.update_color)
        self.color_timer.start(500)  # 每500毫秒变化一次颜色
        self.color_index = 0
        self.colors = [QColor(CYBER_BLUE), QColor(CYBER_GREEN), QColor(CYBER_PINK)]
        
        # 设置初始样式
        self.update_color()
    
    def start_animation(self):
        """开始动画"""
        self.animation.start()
        self.color_timer.start()
    
    def stop_animation(self):
        """停止动画"""
        self.animation.stop()
        self.color_timer.stop()
        self.setValue(0)
    
    def update_color(self):
        """更新颜色"""
        self.color_index = (self.color_index + 1) % len(self.colors)
        color = self.colors[self.color_index]
        self.setStyleSheet(f"""
            QProgressBar {{
                background-color: {BG_DARKER};
                border-radius: 2px;
            }}
            QProgressBar::chunk {{
                background-color: {color.name()};
                border-radius: 2px;
            }}
        """)

# 导入随机模块，用于数据流可视化
import random

class SerialThread(QThread):
    """串口数据接收线程"""
    receive_signal = pyqtSignal(bytes)
    error_signal = pyqtSignal(str)
    
    def __init__(self, serial_port):
        super().__init__()
        self.serial = serial_port
        self.running = False
        
    def run(self):
        global is_serial_thread_recv_flag
        self.running = True
        while self.running:
            try:
                if self.serial.isOpen() and self.serial.in_waiting and is_serial_thread_recv_flag:
                    data = self.serial.read_all()
                    if data:
                        self.receive_signal.emit(data)
            except Exception as e:
                self.error_signal.emit(f"串口接收错误: {str(e)}")
                self.running = False
            time.sleep(0.01)
    
    def stop(self):
        self.running = False
        self.wait()

# 固件下载线程
class FirmwareDownloadThread(QThread):
    """固件下载线程"""
    progress_signal = pyqtSignal(int, int)  # 当前包号, 总包数
    complete_signal = pyqtSignal()  # 下载完成信号
    error_signal = pyqtSignal(str)  # 错误信号
    status_signal = pyqtSignal(str)  # 状态信息信号
    save_signal = pyqtSignal(bytes)  # 保存数据信号
    
    def __init__(self, serial_port, firmware_data,dtype=0, device_addr=0,version=0x0100):
        super().__init__()
        self.serial = serial_port
        self.firmware_data = firmware_data
        self.device_addr = device_addr
        self.dtype = dtype
        self.version = version
        self.running = False
        self.paused = False
        self.total_packets = 0
        self.current_packet = 0
        self.received_data = bytearray()
        self.save_counter = 0  # 接收数据计数器，用于判断是否达到保存阈值
        
    def run(self):
        """运行下载线程"""
        self.running = True
        self.paused = False
        
        try:
            # 计算总包数
            data_length = len(self.firmware_data)
            self.total_packets = (data_length + FIRMWARE_PACKET_SIZE - 1) // FIRMWARE_PACKET_SIZE
            
            # 发送开始下载命令
            if not self.send_start_cmd():
                self.error_signal.emit("固件下载启动失败，设备未响应")
                self.running = False
                return
            
            # 逐包发送数据
            failed_packets = 0  # 记录连续失败的包数
            for i in range(self.total_packets):
                if not self.running:
                    break
                    
                while self.paused and self.running:
                    time.sleep(0.1)  # 暂停时等待
                    
                self.current_packet = i
                
                # 计算当前包数据
                start_pos = i * FIRMWARE_PACKET_SIZE
                end_pos = min(start_pos + FIRMWARE_PACKET_SIZE, data_length)
                packet_data = self.firmware_data[start_pos:end_pos]
                
                # 发送数据包
                if self.send_data_packet(i, packet_data):
                    # 发送成功，重置失败计数
                    failed_packets = 0
                else:
                    # 发送失败，增加失败计数
                    failed_packets += 1
                    # 如果连续5个包发送失败，中止下载
                    if failed_packets >= 5:
                        self.error_signal.emit(f"连续{failed_packets}个数据包发送失败，下载中止")
                        self.running = False
                        break
                
                # 更新进度
                self.progress_signal.emit(i + 1, self.total_packets)
                
                # 每包间隔一小段时间，避免发送过快
                time.sleep(0.05)
            
            # 发送结束命令
            if self.running:
                if self.send_end_cmd():
                    self.complete_signal.emit()
                else:
                    self.error_signal.emit("下载结束命令未得到确认，请检查固件完整性")
                
        except Exception as e:
            self.error_signal.emit(f"固件下载错误: {str(e)}")
            
        self.running = False
    
    def send_start_cmd(self):
        """发送开始下载命令"""
        # 构造数据包: 版本号(4字节) + 文件大小(4字节) + MD5(16字节)
        '''
            unsigned char version[4]; // 版本号码
            unsigned char length[4];  // 固件长度
            unsigned char md5[16];	  // 固件MD5
        '''

        total_packets = self.total_packets
        version = self.version
        file_size = len(self.firmware_data)
        md5_value = hashlib.md5(self.firmware_data).digest()
        
        payload = struct.pack(">II", version, file_size) + md5_value
        success = self.send_command(FIRMWARE_CMD_START, payload)
        
        if success:
            self.status_signal.emit(f"开始下载: 总包数 {total_packets}, 文件大小 {file_size} 字节")
            return True
        else:
            self.status_signal.emit("下载启动失败，无法发送开始命令")
            return False
    
    def send_data_packet(self, packet_index, packet_data):
        """发送数据包"""
        # 构造数据包: 当前包号(2字节) + 总包数(2字节) + 当前包数据长度(2字节) + 包数据
        payload = struct.pack("<HHH", 
                             packet_index,         # 当前包号
                             self.total_packets,   # 总包数
                             len(packet_data)      # 当前包数据长度
                            ) + packet_data
                            
        success = self.send_command(FIRMWARE_CMD_DATA, payload)
        
        # 无论成功与否都记录数据
        # self.save_counter += len(packet_data)
        
        # # 每接收2KB保存一次
        # if self.save_counter >= FIRMWARE_SAVE_THRESHOLD:
        #     self.save_signal.emit(self.received_data)
        #     self.save_counter = 0
        #     if success:
        #         self.status_signal.emit(f"已保存数据块: 包 {packet_index+1}/{self.total_packets}")
        
        return success
    
    def send_end_cmd(self):
        """发送结束下载命令"""
        # 构造数据包: 总包数(2字节) + MD5(16字节)
        # md5_value = hashlib.md5(self.firmware_data).digest()
        # payload = struct.pack("<H", self.total_packets) + md5_value
        payload=b''
        success = self.send_command(FIRMWARE_CMD_END, payload)
            
        if success:
            self.status_signal.emit("下载完成，已发送结束命令并确认")
            return True
        else:
            self.status_signal.emit("下载完成，但结束命令未得到确认或校验失败")
            return False
    
    def send_command(self, cmd, payload):
        """发送命令并等待ACK确认，失败则重试"""
        # 使用xprotocol协议格式发送命令
        # 注: 这里假设外部的xprotocol可用于构建协议包
        # 帧头使用FEED，类型为0, 地址为设备地址，命令为指定的固件命令
        head = bytes.fromhex("FEED")
        dtype = self.dtype
        addr = self.device_addr
        
        try:
            from xprotocol_parser import XProtocol
            xprotocol = XProtocol()
            data_packet = xprotocol.build_packet(head, dtype, addr, cmd, payload)
            
            # 设置最大重试次数
            max_retries = 3
            retry_count = 0
            ack_received = False
            global is_serial_thread_recv_flag
            is_serial_thread_recv_flag=False
            
            while retry_count < max_retries and not ack_received and self.running:
                # 清空串口缓冲区
                self.serial.reset_input_buffer()
                
                # 发送数据
                self.serial.write(data_packet)
                
                # 记录发送时间
                send_time = time.time()
                
                # 等待ACK响应，超时时间2秒
                timeout = 2.0
                while (time.time() - send_time) < timeout and self.running:
                    if self.serial.in_waiting:
                        # 读取响应数据
                        resp_data = self.serial.read_all()
                        if resp_data:
                            # 尝试解析ACK
                            rx_buffer = bytearray(resp_data)
                            packet, consumed = xprotocol.parse_packet(rx_buffer)
                            # 检查是否是对应命令的ACK
                            if packet and packet['cmd'] == (cmd | 0x8000) and packet['crc_valid']:
                                if packet['payload'][0] == 0x00:
                                    self.status_signal.emit(f"命令 0x{cmd:02X} 已确认 (ACK)")
                                    ack_received = True
                                    break
                                else:
                                    self.status_signal.emit(f"命令 0x{cmd:02X} 已确认 (ACK)")
                                    ack_received = True
                                    break
                    
                    # 短暂等待，避免CPU占用过高
                    time.sleep(0.05)
                
                # 如果没有收到ACK，重试
                if not ack_received:
                    retry_count += 1
                    if retry_count < max_retries:
                        self.status_signal.emit(f"命令 0x{cmd:02X} 未收到确认，第{retry_count}次重试...")
                    else:
                        self.status_signal.emit(f"命令 0x{cmd:02X} 失败，已重试{retry_count}次")
                        # 发送错误信号
                        if self.running:  # 如果不是手动中止
                            self.error_signal.emit(f"命令 0x{cmd:02X} 发送失败，没有收到确认响应")
            is_serial_thread_recv_flag=True
            return ack_received
            
        except Exception as e:
            is_serial_thread_recv_flag=True
            self.error_signal.emit(f"发送命令错误: {str(e)}")
            return False
    
    def pause(self):
        """暂停下载"""
        self.paused = True
        self.status_signal.emit("下载已暂停")
    
    def resume(self):
        """恢复下载"""
        self.paused = False
        self.status_signal.emit("下载已继续")
    
    def stop(self):
        """停止下载"""
        self.running = False
        self.wait(1000)  # 等待线程结束，最多1秒
        if self.isRunning():
            self.terminate()  # 强制终止
        self.status_signal.emit("下载已停止")

class RS485Tool(QMainWindow):
    """RS485通信上位机"""
    
    def __init__(self):
        super().__init__()
        self.xprotocol = XProtocol()
        self.serial_port = serial.Serial()
        self.serial_thread = None
        self.rx_buffer = bytearray()
        self.bytes_sent = 0
        self.bytes_received = 0
        self.packet_count = 0
        
        # 固件下载相关属性
        self.firmware_data = None  # 固件数据
        self.firmware_path = ""    # 固件文件路径
        self.firmware_md5 = ""     # 固件MD5值
        self.firmware_size = 0     # 固件大小
        self.firmware_download_thread = None  # 固件下载线程
        self.received_firmware = bytearray()  # 接收的固件数据
        
        # 加载自定义字体
        self.load_fonts()
        
        # 设置应用样式
        self.set_application_style()
        
        # 初始化UI
        self.init_ui()
        
        # 创建状态栏
        self.create_status_bar()
        
        # 添加闪烁效果的计时器
        self.blink_timer = QTimer(self)
        self.blink_timer.timeout.connect(self.update_blink_effect)
        self.blink_timer.start(500)  # 每500毫秒更新一次闪烁效果
        self.blink_state = False
        
        # 初始化统计数据定时器
        self.stats_timer = QTimer(self)
        self.stats_timer.timeout.connect(self.update_stats)
        self.stats_timer.start(1000)  # 每秒更新一次统计数据
        
        # 界面初始化完成后的后期处理
        QTimer.singleShot(100, self.post_initialization)
    
    def load_fonts(self):
        """加载自定义字体"""
        # 在实际应用中，可以加载真实的字体文件
        # 这里只是一个占位符
        pass
    
    def post_initialization(self):
        """界面初始化完成后的后期处理"""
        # 添加启动动画
        self.run_startup_animation()
    
    def run_startup_animation(self):
        """运行启动动画"""
        # 创建波纹动画
        self.statusBar().showMessage("系统初始化完成")
        
        # 模拟系统启动时的进度显示
        for lcd in [self.sent_lcd, self.received_lcd, self.packet_lcd]:
            for i in range(0, 101, 20):
                lcd.display(i)
                QApplication.processEvents()
                time.sleep(0.02)
            lcd.display(0)
    
    def set_application_style(self):
        """设置应用程序的整体样式"""
        # 设置暗色主题
        dark_palette = QPalette()
        dark_palette.setColor(QPalette.Window, QColor(BG_DARK))
        dark_palette.setColor(QPalette.WindowText, Qt.white)
        dark_palette.setColor(QPalette.Base, QColor(BG_DARKER))
        dark_palette.setColor(QPalette.AlternateBase, QColor(BG_DARK))
        dark_palette.setColor(QPalette.ToolTipBase, QColor(BG_DARKER))
        dark_palette.setColor(QPalette.ToolTipText, Qt.white)
        dark_palette.setColor(QPalette.Text, Qt.white)
        dark_palette.setColor(QPalette.Button, QColor(BG_DARK))
        dark_palette.setColor(QPalette.ButtonText, Qt.white)
        dark_palette.setColor(QPalette.BrightText, QColor(CYBER_YELLOW))
        dark_palette.setColor(QPalette.Link, QColor(CYBER_BLUE))
        dark_palette.setColor(QPalette.Highlight, QColor(CYBER_GREEN))
        dark_palette.setColor(QPalette.HighlightedText, Qt.black)
        
        QApplication.setPalette(dark_palette)
        
        # 设置应用程序样式表
        qss = f"""
        QMainWindow {{
            background-color: {BG_DARK};
        }}
        QWidget {{
            color: #FFFFFF;
            background-color: {BG_DARK};
            font-family: 'Segoe UI', Arial, sans-serif;
        }}
        QPushButton {{
            background-color: {BG_HIGHLIGHT};
            border: 1px solid {CYBER_BLUE};
            color: white;
            padding: 5px;
            border-radius: 3px;
            font-weight: bold;
        }}
        QPushButton:hover {{
            background-color: {CYBER_BLUE};
            color: black;
        }}
        QPushButton:pressed {{
            background-color: #005F8F;
        }}
        QPushButton:disabled {{
            background-color: #555555;
            color: #999999;
            border: 1px solid #777777;
        }}
        QComboBox {{
            border: 1px solid {CYBER_BLUE};
            padding: 3px;
            border-radius: 3px;
            background-color: {BG_HIGHLIGHT};
        }}
        QComboBox::drop-down {{
            border: none;
            background-color: {CYBER_BLUE};
            width: 20px;
        }}
        QComboBox QAbstractItemView {{
            border: 1px solid {CYBER_BLUE};
            background-color: {BG_HIGHLIGHT};
            selection-background-color: {CYBER_BLUE};
        }}
        QLineEdit {{
            padding: 5px;
            border: 1px solid {CYBER_BLUE};
            border-radius: 3px;
            background-color: {BG_HIGHLIGHT};
        }}
        QTextEdit {{
            border: 1px solid #555555;
            border-radius: 3px;
            background-color: {BG_DARKER};
            font-family: 'Consolas', 'Courier New', monospace;
        }}
        QGroupBox {{
            font-weight: bold;
            border: 1px solid {CYBER_BLUE};
            border-radius: 5px;
            margin-top: 10px;
            padding-top: 15px;
        }}
        QGroupBox::title {{
            subcontrol-origin: margin;
            subcontrol-position: top center;
            padding: 0 8px;
            color: {CYBER_BLUE};
            font-family: 'Orbitron', 'Segoe UI', Arial, sans-serif;
        }}
        QCheckBox {{
            spacing: 5px;
        }}
        QCheckBox::indicator {{
            width: 16px;
            height: 16px;
        }}
        QCheckBox::indicator:unchecked {{
            border: 1px solid {CYBER_BLUE};
            background-color: {BG_HIGHLIGHT};
            border-radius: 3px;
        }}
        QCheckBox::indicator:checked {{
            border: 1px solid {CYBER_BLUE};
            background-color: {CYBER_BLUE};
            border-radius: 3px;
        }}
        QLabel {{
            color: #CCCCCC;
        }}
        QFrame#line_frame {{
            background-color: {CYBER_BLUE};
        }}
        QToolButton {{
            background-color: transparent;
            border: none;
        }}
        QSplitter::handle {{
            background-color: {CYBER_BLUE};
        }}
        QLCDNumber {{
            background-color: {BG_DARKER};
            color: {CYBER_GREEN};
            border: 1px solid {CYBER_BLUE};
            border-radius: 3px;
        }}
        QTabWidget::pane {{
            border: 1px solid {CYBER_BLUE};
            border-radius: 5px;
            background-color: {BG_PANEL};
        }}
        QTabBar::tab {{
            background-color: {BG_HIGHLIGHT};
            color: white;
            padding: 6px 12px;
            margin-right: 2px;
            border-top-left-radius: 4px;
            border-top-right-radius: 4px;
        }}
        QTabBar::tab:selected {{
            background-color: {CYBER_BLUE};
            color: black;
        }}
        QTabBar::tab:!selected:hover {{
            background-color: #3A3A3A;
        }}
        QStatusBar {{
            background-color: {BG_DARKER};
            color: #CCCCCC;
            border-top: 1px solid {CYBER_BLUE};
        }}
        QScrollBar:vertical {{
            border: none;
            background-color: {BG_DARKER};
            width: 12px;
            margin: 15px 0 15px 0;
        }}
        QScrollBar::handle:vertical {{
            background-color: {CYBER_BLUE};
            min-height: 30px;
            border-radius: 6px;
        }}
        QScrollBar::sub-line:vertical, QScrollBar::add-line:vertical {{
            border: none;
            background: none;
            height: 15px;
            subcontrol-position: top;
            subcontrol-origin: margin;
        }}
        QScrollBar::sub-page:vertical, QScrollBar::add-page:vertical {{
            background: none;
        }}
        """
        
        self.setStyleSheet(qss)
        
        # 设置字体
        font = QApplication.font()
        font.setFamily("Segoe UI")
        font.setPointSize(9)
        QApplication.setFont(font)
    
    def create_status_bar(self):
        """创建状态栏"""
        self.statusBar().showMessage("系统就绪")
        
        # 添加活动指示器
        self.activity_indicator = BlinkingProgressBar()
        self.activity_indicator.setFixedWidth(100)
        self.statusBar().addPermanentWidget(self.activity_indicator)
        
        # 添加连接状态指示器
        self.connection_status = NeonLabel("未连接", color=CYBER_PINK)
        self.statusBar().addPermanentWidget(self.connection_status)
    
    def update_blink_effect(self):
        """更新闪烁效果"""
        if self.serial_port.isOpen():
            self.blink_state = not self.blink_state
            if self.blink_state:
                self.connection_status.setStyleSheet(f"color: {CYBER_GREEN};")
            else:
                self.connection_status.setStyleSheet(f"color: #00CC00;")
            self.connection_status.setText("已连接")
        else:
            self.connection_status.setStyleSheet(f"color: {CYBER_PINK};")
            self.connection_status.setText("未连接")
    
    def update_stats(self):
        """更新统计数据"""
        if hasattr(self, 'sent_lcd'):
            self.sent_lcd.display(self.bytes_sent)
        if hasattr(self, 'received_lcd'):
            self.received_lcd.display(self.bytes_received)
        if hasattr(self, 'packet_lcd'):
            self.packet_lcd.display(self.packet_count)
        
    def create_combined_top_panel(self, parent_layout):
        """创建合并了软件信息和串口设置的顶部面板"""
        top_panel = QGroupBox("系统信息与串口设置")
        top_panel.setStyleSheet(f"""
            QGroupBox {{
                font-weight: bold;
                border: 1px solid {CYBER_BLUE};
                border-radius: 5px;
                margin-top: 10px;
                padding-top: 15px;
            }}
            QGroupBox::title {{
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 8px;
                color: {CYBER_BLUE};
                font-family: 'Orbitron', 'Segoe UI', Arial, sans-serif;
            }}
        """)
        
        # 添加阴影效果
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(20)
        shadow.setColor(QColor(CYBER_BLUE).darker(150))
        shadow.setOffset(0, 0)
        top_panel.setGraphicsEffect(shadow)
        
        # 使用网格布局来组织元素
        top_layout = QGridLayout(top_panel)
        top_layout.setColumnStretch(0, 2)  # 第一列（标题和协议信息）占更多空间
        top_layout.setColumnStretch(1, 2)  # 第二列（串口设置）占中等空间
        top_layout.setColumnStretch(2, 1)  # 第三列（统计信息）占较少空间
        
        # ------ 第一列：软件标题和信息 ------
        # 标题标签
        title_label = NeonLabel("RS485通信上位机", color=CYBER_BLUE)
        title_label.setStyleSheet(f"""
            font-family: 'Orbitron', 'Arial', sans-serif;
            font-size: 24px;
            font-weight: bold;
            color: {CYBER_BLUE};
        """)
        
        # 更强的发光效果
        title_glow = QGraphicsDropShadowEffect()
        title_glow.setBlurRadius(15)
        title_glow.setColor(QColor(CYBER_BLUE))
        title_glow.setOffset(0, 0)
        title_label.setGraphicsEffect(title_glow)
        
        top_layout.addWidget(title_label, 0, 0)
        
        # 状态和时间信息
        status_container = QWidget()
        status_layout = QHBoxLayout(status_container)
        status_layout.setContentsMargins(0, 0, 0, 0)
        
        status_layout.addWidget(QLabel("状态:"))
        
        self.status_label = QLabel("就绪")
        self.status_label.setStyleSheet(f"color: {CYBER_GREEN}; font-weight: bold;")
        status_layout.addWidget(self.status_label)
        
        # 显示当前时间
        status_layout.addStretch()
        self.time_label = QLabel(time.strftime("%H:%M:%S"))
        self.time_label.setStyleSheet("color: #BBBBBB;")
        status_layout.addWidget(self.time_label)
        
        # 更新时间的定时器
        time_timer = QTimer(self)
        time_timer.timeout.connect(self.update_time)
        time_timer.start(1000)  # 每秒更新一次
        
        top_layout.addWidget(status_container, 1, 0)
        
        # 协议信息
        protocol_label = QLabel("协议: xprotocol (帧头-长度-类型-地址-命令-数据-CRC)")
        protocol_label.setStyleSheet(f"color: {CYBER_YELLOW}; font-style: italic;")
        top_layout.addWidget(protocol_label, 2, 0)
        
        # ------ 第二列：串口设置 ------
        # 创建串口选择和波特率设置
        serial_container = QWidget()
        serial_layout = QGridLayout(serial_container)
        serial_layout.setContentsMargins(0, 0, 0, 0)
        
        # 串口选择
        serial_layout.addWidget(QLabel("串口:"), 0, 0)
        self.port_combo = QComboBox()
        self.port_combo.setMinimumWidth(120)
        serial_layout.addWidget(self.port_combo, 0, 1)
        
        # 波特率设置
        serial_layout.addWidget(QLabel("波特率:"), 1, 0)
        self.baud_combo = QComboBox()
        self.baud_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
        self.baud_combo.setCurrentText("115200")
        self.baud_combo.setMinimumWidth(120)
        serial_layout.addWidget(self.baud_combo, 1, 1)
        
        top_layout.addWidget(serial_container, 0, 1, 2, 1)  # 占据第二列的前两行
        
        # 串口操作按钮
        button_container = QWidget()
        button_layout = QHBoxLayout(button_container)
        button_layout.setContentsMargins(0, 0, 0, 0)
        
        # 刷新串口按钮
        refresh_btn = TechButton("刷新串口", accent_color=CYBER_BLUE)
        refresh_btn.clicked.connect(self.refresh_serial_ports)
        refresh_btn.setMinimumWidth(100)
        button_layout.addWidget(refresh_btn)
        
        # 串口开关按钮
        self.connect_btn = TechButton("打开串口", accent_color=CYBER_GREEN)
        self.connect_btn.clicked.connect(self.toggle_serial)
        self.connect_btn.setMinimumWidth(100)
        button_layout.addWidget(self.connect_btn)
        
        top_layout.addWidget(button_container, 2, 1)  # 放在第二列第三行
        
        # ------ 第三列：统计信息 ------
        stats_panel = QFrame()
        stats_panel.setStyleSheet(f"""
            background-color: {BG_DARKER};
            border-radius: 5px;
        """)
        
        stats_layout = QGridLayout(stats_panel)
        stats_layout.setContentsMargins(10, 5, 10, 5)
        stats_layout.setSpacing(5)
        
        # 添加发送字节统计
        stats_layout.addWidget(QLabel("发送:"), 0, 0)
        self.sent_lcd = QLCDNumber(6)
        self.sent_lcd.setSegmentStyle(QLCDNumber.Flat)
        self.sent_lcd.setDigitCount(6)
        stats_layout.addWidget(self.sent_lcd, 0, 1)
        
        # 添加接收字节统计
        stats_layout.addWidget(QLabel("接收:"), 1, 0)
        self.received_lcd = QLCDNumber(6)
        self.received_lcd.setSegmentStyle(QLCDNumber.Flat)
        self.received_lcd.setDigitCount(6)
        stats_layout.addWidget(self.received_lcd, 1, 1)
        
        # 添加数据包统计
        stats_layout.addWidget(QLabel("数据包:"), 2, 0)
        self.packet_lcd = QLCDNumber(6)
        self.packet_lcd.setSegmentStyle(QLCDNumber.Flat)
        self.packet_lcd.setDigitCount(6)
        stats_layout.addWidget(self.packet_lcd, 2, 1)
        
        top_layout.addWidget(stats_panel, 0, 2, 3, 1)  # 占据第三列的全部三行
        
        parent_layout.addWidget(top_panel)
    
    def update_time(self):
        """更新时间显示"""
        self.time_label.setText(time.strftime("%H:%M:%S"))
    
    def create_separator(self):
        """创建带有科技感的分隔符"""
        separator = QFrame()
        separator.setFrameShape(QFrame.HLine)
        separator.setFixedHeight(2)
        separator.setStyleSheet(f"""
            background: qlineargradient(x1:0, y1:0, x2:1, y2:0, 
                stop:0 {BG_DARK}, 
                stop:0.4 {CYBER_BLUE}, 
                stop:0.6 {CYBER_BLUE}, 
                stop:1 {BG_DARK});
        """)
        return separator
        
    def init_ui(self):
        """初始化UI界面"""
        self.setWindowTitle("RS485通信上位机 - 高级版")
        self.setGeometry(100, 100, 1000, 750)  # 适当增加窗口高度
        
        # 主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(12)  # 统一间距
        main_layout.setContentsMargins(15, 15, 15, 15)
        
        # 添加顶部面板（合并软件信息和串口设置）
        self.create_combined_top_panel(main_layout)
        
        # 添加分隔符
        main_layout.addWidget(self.create_separator())
        
        # 添加数据流可视化组件
        flow_group = QGroupBox("数据流")
        flow_layout = QVBoxLayout()
        
        self.data_flow = DataFlowVisualizer()
        flow_layout.addWidget(self.data_flow)
        
        flow_group.setLayout(flow_layout)
        flow_group.setMaximumHeight(80)  # 限制数据流可视化组件高度
        main_layout.addWidget(flow_group)
        
        # 创建主界面分割区域 - 使用水平分割
        main_content = QSplitter(Qt.Horizontal)  # 水平分割
        main_content.setChildrenCollapsible(False)
        main_content.setHandleWidth(5)
        main_content.setStyleSheet(f"""
            QSplitter::handle {{
                background: {CYBER_BLUE};
                width: 2px;  /* 水平分割时应该设置宽度而不是高度 */
            }}
        """)
        
        # ===== 左侧：发送区域 =====
        send_group = QGroupBox("数据发送")
        send_layout = QVBoxLayout()
        send_layout.setSpacing(12)
        send_layout.setContentsMargins(10, 15, 10, 10)
        
        # 发送区域表单布局
        form_container = QWidget()
        protocol_form = QFormLayout(form_container)
        protocol_form.setSpacing(12)
        protocol_form.setContentsMargins(5, 5, 5, 5)
        protocol_form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
        protocol_form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
        
        # 帧头输入
        self.head_input = QLineEdit("FEED")
        self.head_input.setMaxLength(4)
        self.head_input.setPlaceholderText("16进制帧头，如:FEED")
        hex_validator = HexValidator()
        self.head_input.setValidator(hex_validator)
        protocol_form.addRow(self.styled_label("帧头(2字节):"), self.head_input)
        
        # 数据类型输入
        self.dtype_input = QLineEdit("0031")
        self.dtype_input.setValidator(hex_validator)
        self.dtype_input.setPlaceholderText("16进制设备类型，如:0001")
        self.dtype_input.setMaxLength(4)
        protocol_form.addRow(self.styled_label("设备类型(2字节):"), self.dtype_input)
        
        # 地址输入
        self.addr_input = QLineEdit("0d19")
        self.addr_input.setValidator(hex_validator)
        self.addr_input.setPlaceholderText("16进制地址，如:0001")
        self.addr_input.setMaxLength(4)
        protocol_form.addRow(self.styled_label("地址(2字节):"), self.addr_input)
        
        # 命令输入
        self.cmd_input = QLineEdit("0001")
        self.cmd_input.setValidator(hex_validator)
        self.cmd_input.setPlaceholderText("16进制命令，如:0001")
        self.cmd_input.setMaxLength(4)
        protocol_form.addRow(self.styled_label("命令(2字节):"), self.cmd_input)
        
        # 负载数据输入
        self.payload_input = QLineEdit()
        self.payload_input.setPlaceholderText("填写16进制数据，如：01020304")
        protocol_form.addRow(self.styled_label("数据负载(N字节):"), self.payload_input)
        
        # CRC设置
        crc_container = QWidget()
        crc_layout = QHBoxLayout(crc_container)
        crc_layout.setContentsMargins(0, 0, 0, 0)
        
        self.auto_crc = QCheckBox("自动计算CRC")
        self.auto_crc.setChecked(True)
        crc_layout.addWidget(self.auto_crc)
        
        self.crc_input = QLineEdit()
        self.crc_input.setEnabled(False)
        self.crc_input.setPlaceholderText("自动计算CRC校验值")
        crc_layout.addWidget(self.crc_input)
        
        protocol_form.addRow(self.styled_label("CRC(2字节):"), crc_container)
        
        send_layout.addWidget(form_container)
        
        # 发送区域底部按钮
        send_btn_layout = QHBoxLayout()
        send_btn_layout.setSpacing(10)
        
        # 添加一些弹性空间，使按钮居中
        send_btn_layout.addStretch(1)
        
        self.send_btn = TechButton("发送数据", accent_color=CYBER_GREEN)
        self.send_btn.clicked.connect(self.send_data)
        self.send_btn.setMinimumWidth(120)
        send_btn_layout.addWidget(self.send_btn)
        
        self.clear_send_btn = TechButton("清空发送区", accent_color=CYBER_YELLOW)
        self.clear_send_btn.clicked.connect(self.clear_send_area)
        self.clear_send_btn.setMinimumWidth(120)
        send_btn_layout.addWidget(self.clear_send_btn)
        
        # 添加预设按钮
        preset_btn = TechButton("常用命令", accent_color=CYBER_PURPLE)
        preset_btn.clicked.connect(self.show_presets_menu)
        preset_btn.setMinimumWidth(120)
        send_btn_layout.addWidget(preset_btn)
        
        # 添加一些弹性空间，使按钮居中
        send_btn_layout.addStretch(1)
        
        send_layout.addLayout(send_btn_layout)
        
        # 添加进度提示条
        self.send_progress = CyberProgressBar(color=CYBER_GREEN)
        self.send_progress.setFixedHeight(4)
        self.send_progress.setValue(0)
        send_layout.addWidget(self.send_progress)
        
        send_group.setLayout(send_layout)
        
        # ===== 右侧：接收区域 =====
        recv_tabs = QTabWidget()
        recv_tabs.setTabPosition(QTabWidget.South)
        recv_tabs.setDocumentMode(True)  # 使选项卡看起来更现代
        
        # ===== 数据接收选项卡 =====
        raw_tab = QWidget()
        raw_layout = QVBoxLayout(raw_tab)
        raw_layout.setSpacing(10)
        raw_layout.setContentsMargins(10, 10, 10, 10)
        
        self.recv_text = QTextEdit()
        self.recv_text.setReadOnly(True)
        self.recv_text.setStyleSheet(f"""
            QTextEdit {{
                background-color: {BG_DARKER};
                color: {CYBER_GREEN};
                font-family: 'Consolas', 'Courier New', monospace;
                font-size: 12px;
                border: 1px solid {CYBER_BLUE};
                border-radius: 5px;
                padding: 5px;
            }}
        """)
        raw_layout.addWidget(self.recv_text)
        
        # 接收区域底部按钮 - 使用QHBoxLayout
        recv_btn_layout = QHBoxLayout()
        recv_btn_layout.setSpacing(10)
        
        # 左侧复选框选项组
        options_layout = QHBoxLayout()
        options_layout.setSpacing(15)
        
        self.hex_display = QCheckBox("16进制显示")
        self.hex_display.setChecked(True)
        options_layout.addWidget(self.hex_display)
        
        self.parse_protocol = QCheckBox("解析协议")
        self.parse_protocol.setChecked(True)
        options_layout.addWidget(self.parse_protocol)
        
        self.auto_scroll = QCheckBox("自动滚动")
        self.auto_scroll.setChecked(True)
        options_layout.addWidget(self.auto_scroll)
        
        recv_btn_layout.addLayout(options_layout)
        
        # 添加弹性空间，使右侧按钮靠右显示
        recv_btn_layout.addStretch(1)
        
        # 右侧操作按钮组
        actions_layout = QHBoxLayout()
        actions_layout.setSpacing(10)
        
        # 添加保存数据按钮
        self.save_btn = TechButton("保存数据", accent_color=CYBER_BLUE)
        self.save_btn.clicked.connect(self.save_data)
        self.save_btn.setMinimumWidth(100)
        actions_layout.addWidget(self.save_btn)
        
        self.clear_recv_btn = TechButton("清空接收区", accent_color=CYBER_PINK)
        self.clear_recv_btn.clicked.connect(self.clear_recv_area)
        self.clear_recv_btn.setMinimumWidth(100)
        actions_layout.addWidget(self.clear_recv_btn)
        
        recv_btn_layout.addLayout(actions_layout)
        
        raw_layout.addLayout(recv_btn_layout)
        
        # 添加接收进度条
        self.recv_progress = CyberProgressBar(color=CYBER_GREEN)
        self.recv_progress.setFixedHeight(4)
        self.recv_progress.setValue(0)
        raw_layout.addWidget(self.recv_progress)
        
        recv_tabs.addTab(raw_tab, "数据接收")
        
        # ===== 数据包分析选项卡 =====
        packets_tab = QWidget()
        packets_layout = QVBoxLayout(packets_tab)
        packets_layout.setSpacing(10)
        packets_layout.setContentsMargins(10, 10, 10, 10)
        
        self.packets_text = QTextEdit()
        self.packets_text.setReadOnly(True)
        self.packets_text.setStyleSheet(f"""
            QTextEdit {{
                background-color: {BG_DARKER};
                color: {CYBER_BLUE};
                font-family: 'Consolas', 'Courier New', monospace;
                font-size: 12px;
                border: 1px solid {CYBER_BLUE};
                border-radius: 5px;
                padding: 5px;
            }}
        """)
        packets_layout.addWidget(self.packets_text)
        
        # 数据包选项卡底部按钮
        packets_btn_layout = QHBoxLayout()
        packets_btn_layout.setSpacing(15)
        
        # 左侧筛选选项
        filter_layout = QHBoxLayout()
        filter_layout.setSpacing(5)
        filter_layout.addWidget(QLabel("筛选:"))
        
        self.filter_combo = QComboBox()
        self.filter_combo.addItems(["全部数据包", "仅有效数据包", "仅无效数据包"])
        self.filter_combo.setMinimumWidth(150)
        filter_layout.addWidget(self.filter_combo)
        
        packets_btn_layout.addLayout(filter_layout)
        
        # 右侧按钮
        packets_btn_layout.addStretch(1)
        
        self.clear_packets_btn = TechButton("清空", accent_color=CYBER_PINK)
        self.clear_packets_btn.clicked.connect(lambda: self.packets_text.clear())
        self.clear_packets_btn.setMinimumWidth(100)
        packets_btn_layout.addWidget(self.clear_packets_btn)
        
        packets_layout.addLayout(packets_btn_layout)
        
        recv_tabs.addTab(packets_tab, "数据包分析")
        
        # ===== 统计选项卡 =====
        stats_tab = QWidget()
        stats_layout = QVBoxLayout(stats_tab)
        stats_layout.setContentsMargins(10, 10, 10, 10)
        
        stats_text = QTextEdit()
        stats_text.setReadOnly(True)
        stats_text.setHtml(f"""
        <div style='color:{CYBER_YELLOW}; font-family: Consolas, Courier New, monospace;'>
            <h3 style='color:{CYBER_BLUE};'>RS485通信统计</h3>
            <p>此功能将显示通信过程中的统计数据和趋势图。</p>
            <ul>
                <li>数据包成功率</li>
                <li>通信速率</li>
                <li>错误统计</li>
                <li>CRC校验分析</li>
            </ul>
            <p style='color:{CYBER_PINK};'>在后续版本中实现...</p>
        </div>
        """)
        stats_layout.addWidget(stats_text)
        
        recv_tabs.addTab(stats_tab, "统计分析")
        
        # ===== 固件下载选项卡 =====
        firmware_tab = QWidget()
        firmware_layout = QVBoxLayout(firmware_tab)
        firmware_layout.setSpacing(15)
        firmware_layout.setContentsMargins(10, 10, 10, 10)
        
        # 上部分：文件选择和信息显示
        upper_layout = QHBoxLayout()
        
        # 文件选择区域
        file_group = QGroupBox("固件文件")
        file_layout = QVBoxLayout()  # 改为垂直布局更清晰
        file_layout.setSpacing(10)
        
        # 文件路径显示 - 使用水平布局
        file_path_layout = QHBoxLayout()
        file_path_layout.addWidget(QLabel("文件路径:"))
        
        self.firmware_path_edit = QLineEdit()
        self.firmware_path_edit.setReadOnly(True)
        self.firmware_path_edit.setPlaceholderText("请选择固件文件...")
        file_path_layout.addWidget(self.firmware_path_edit, 1)  # 设置拉伸因子
        
        # 选择文件按钮
        select_file_btn = TechButton("选择文件", accent_color=CYBER_PURPLE)
        select_file_btn.clicked.connect(self.select_firmware_file)
        file_path_layout.addWidget(select_file_btn)
        
        file_layout.addLayout(file_path_layout)
        
        # 文件信息显示 - 使用网格布局
        file_info_layout = QGridLayout()
        file_info_layout.setSpacing(10)
        
        file_info_layout.addWidget(QLabel("文件大小:"), 0, 0)
        self.firmware_size_label = QLabel("0 字节")
        file_info_layout.addWidget(self.firmware_size_label, 0, 1)
        
        file_info_layout.addWidget(QLabel("MD5校验:"), 0, 2)
        self.firmware_md5_label = QLabel("-")
        self.firmware_md5_label.setStyleSheet(f"color: {CYBER_GREEN}; font-family: 'Consolas', monospace;")
        file_info_layout.addWidget(self.firmware_md5_label, 0, 3)
        
        file_layout.addLayout(file_info_layout)
        
        file_group.setLayout(file_layout)
        firmware_layout.addWidget(file_group)
        
        # 中部分：下载设置和进度
        mid_section = QHBoxLayout()
        
        # 下载设置区域
        dl_settings_group = QGroupBox("下载设置")
        dl_settings_layout = QGridLayout()
        dl_settings_layout.setSpacing(15)
        
        dl_settings_layout.addWidget(QLabel("版本号:"), 0, 0)
        self.firmware_version_input = QLineEdit("0000")
        self.firmware_version_input.setValidator(HexValidator())
        self.firmware_version_input.setMaxLength(4)
        self.firmware_version_input.setPlaceholderText("16进制地址")
        dl_settings_layout.addWidget(self.firmware_version_input, 0, 1)
        
        dl_settings_layout.addWidget(QLabel("包大小:"), 0, 2)
        self.packet_size_label = QLabel(f"{FIRMWARE_PACKET_SIZE} 字节")
        dl_settings_layout.addWidget(self.packet_size_label, 0, 3)
        
        dl_settings_layout.addWidget(QLabel("保存阈值:"), 1, 0)
        self.save_threshold_label = QLabel(f"{FIRMWARE_SAVE_THRESHOLD} 字节")
        dl_settings_layout.addWidget(self.save_threshold_label, 1, 1)
        
        dl_settings_layout.addWidget(QLabel("自动计算包数:"), 1, 2)
        self.packet_count_label = QLabel("0 包")
        dl_settings_layout.addWidget(self.packet_count_label, 1, 3)
        
        dl_settings_group.setLayout(dl_settings_layout)
        firmware_layout.addWidget(dl_settings_group)
        
        # 下载进度区域
        progress_group = QGroupBox("下载进度")
        progress_layout = QVBoxLayout()
        progress_layout.setSpacing(10)
        
        # 进度条
        self.firmware_progress = QProgressBar()
        self.firmware_progress.setRange(0, 100)
        self.firmware_progress.setValue(0)
        self.firmware_progress.setTextVisible(True)
        self.firmware_progress.setFormat("%v/%m 包 (%p%)")
        self.firmware_progress.setStyleSheet(f"""
            QProgressBar {{
                border: 1px solid {CYBER_BLUE};
                border-radius: 5px;
                background-color: {BG_DARKER};
                text-align: center;
                height: 25px;
                font-weight: bold;
            }}
            
            QProgressBar::chunk {{
                background-color: {CYBER_GREEN};
                border-radius: 4px;
            }}
        """)
        progress_layout.addWidget(self.firmware_progress)
        
        # 状态显示
        progress_layout.addWidget(QLabel("状态:"))
        self.firmware_status = QTextEdit()
        self.firmware_status.setReadOnly(True)
        self.firmware_status.setMaximumHeight(100)
        self.firmware_status.setStyleSheet(f"""
            background-color: {BG_DARKER};
            color: {CYBER_YELLOW};
            border: 1px solid {CYBER_BLUE};
            border-radius: 5px;
            padding: 5px;
        """)
        progress_layout.addWidget(self.firmware_status)
        
        # 操作按钮 - 水平居中排列
        btn_layout = QHBoxLayout()
        btn_layout.setSpacing(15)
        
        # 添加弹性空间，使按钮居中
        btn_layout.addStretch(1)
        
        self.download_btn = TechButton("开始下载", accent_color=CYBER_GREEN)
        self.download_btn.clicked.connect(self.start_firmware_download)
        self.download_btn.setMinimumWidth(120)
        btn_layout.addWidget(self.download_btn)
        
        self.pause_btn = TechButton("暂停", accent_color=CYBER_YELLOW)
        self.pause_btn.clicked.connect(self.toggle_firmware_download)
        self.pause_btn.setEnabled(False)
        self.pause_btn.setMinimumWidth(120)
        btn_layout.addWidget(self.pause_btn)
        
        self.stop_btn = TechButton("停止", accent_color=CYBER_PINK)
        self.stop_btn.clicked.connect(self.stop_firmware_download)
        self.stop_btn.setEnabled(False)
        self.stop_btn.setMinimumWidth(120)
        btn_layout.addWidget(self.stop_btn)
        
        # 添加弹性空间，使按钮居中
        btn_layout.addStretch(1)
        
        progress_layout.addLayout(btn_layout)
        
        progress_group.setLayout(progress_layout)
        firmware_layout.addWidget(progress_group)
        
        recv_tabs.addTab(firmware_tab, "固件下载")
        
        # 将左侧发送区域和右侧接收区域添加到分割器
        main_content.addWidget(send_group)
        main_content.addWidget(recv_tabs)
        
        # 设置分割比例 - 左侧占40%，右侧占60%
        main_content.setSizes([400, 600])
        
        # 将主内容区域添加到主布局
        main_layout.addWidget(main_content)
        
        # 初始化
        self.refresh_serial_ports()
    
    def show_presets_menu(self):
        """显示预设命令菜单"""
        menu = QMenu(self)
        
        # 添加一些预设命令
        actions = [
            ("读取设备信息", lambda: self.load_preset("0000", "0001", "0001", "")),
            ("读取状态", lambda: self.load_preset("0000", "0001", "0002", "")),
            ("设置地址", lambda: self.load_preset("0000", "0002", "0003", "0001")),
            ("重启设备", lambda: self.load_preset("0000", "0003", "00FF", ""))
        ]
        
        for name, handler in actions:
            action = QAction(name, self)
            action.triggered.connect(handler)
            menu.addAction(action)
        
        # 在按钮位置显示菜单
        menu.exec_(self.sender().mapToGlobal(QPoint(0, self.sender().height())))
    
    def load_preset(self, dtype, addr, cmd, payload):
        """加载预设命令"""
        self.dtype_input.setText(dtype)
        self.addr_input.setText(addr)
        self.cmd_input.setText(cmd)
        self.payload_input.setText(payload)
    
    def styled_label(self, text):
        """创建带样式的标签"""
        label = QLabel(text)
        label.setStyleSheet(f"color: {CYBER_BLUE}; font-weight: bold;")
        return label
    
    def create_icon(self, icon_type):
        """创建图标"""
        # 这里只是返回null，实际应用中可以添加图标资源
        return QIcon()
    
    def refresh_serial_ports(self):
        """刷新可用串口列表"""
        self.port_combo.clear()
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_combo.addItem(port.device)
    
    def toggle_serial(self):
        """打开/关闭串口"""
        if not self.serial_port.isOpen():
            try:
                port = self.port_combo.currentText()
                baud = int(self.baud_combo.currentText())
                self.serial_port = serial.Serial(port, baud, timeout=0.1)
                
                # 启动串口接收线程
                self.serial_thread = SerialThread(self.serial_port)
                self.serial_thread.receive_signal.connect(self.handle_received_data)
                self.serial_thread.error_signal.connect(self.show_error)
                self.serial_thread.start()
                
                # 启动状态条动画
                self.activity_indicator.start_animation()
                
                self.connect_btn.setText("关闭串口")
                self.connect_btn.setStyleSheet(f"background-color: {CYBER_PINK};")
                self.statusBar().showMessage("串口已打开")
                self.status_label.setText("已连接")
                self.status_label.setStyleSheet(f"color: {CYBER_GREEN}; font-weight: bold;")
                self.add_message_to_recv("串口已打开", "system")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"打开串口失败: {str(e)}")
        else:
            try:
                if self.serial_thread:
                    self.serial_thread.stop()
                    self.serial_thread = None
                
                self.serial_port.close()
                
                # 停止状态条动画
                self.activity_indicator.stop_animation()
                
                self.connect_btn.setText("打开串口")
                self.connect_btn.setStyleSheet("")
                self.statusBar().showMessage("串口已关闭")
                self.status_label.setText("未连接")
                self.status_label.setStyleSheet(f"color: {CYBER_PINK}; font-weight: bold;")
                self.add_message_to_recv("串口已关闭", "system")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"关闭串口失败: {str(e)}")
    
    def add_message_to_recv(self, message, msg_type="normal"):
        """向接收区添加消息"""
        if msg_type == "normal":
            self.recv_text.setTextColor(QColor(CYBER_GREEN))
        elif msg_type == "system":
            self.recv_text.setTextColor(QColor(CYBER_YELLOW))
        elif msg_type == "error":
            self.recv_text.setTextColor(QColor(CYBER_PINK))
        else:
            self.recv_text.setTextColor(QColor("#FFFFFF"))
            
        self.recv_text.append(message)
        
        # 自动滚动
        if self.auto_scroll.isChecked():
            self.recv_text.moveCursor(QTextCursor.End)
    
    def send_data(self):
        """发送数据"""
        if not self.serial_port.isOpen():
            QMessageBox.warning(self, "警告", "请先打开串口")
            return
        
        try:
            # 获取协议参数并转换为16进制
            head = bytes.fromhex(self.head_input.text())
            
            # 转换数据类型 (16进制字符串 -> int -> bytes)
            dtype_text = self.dtype_input.text().lower()
            if dtype_text.startswith('0x'):
                dtype_text = dtype_text[2:]
            dtype = int(dtype_text, 16)
            
            # 转换地址 (16进制字符串 -> int -> bytes)
            addr_text = self.addr_input.text().lower()
            if addr_text.startswith('0x'):
                addr_text = addr_text[2:]
            addr = int(addr_text, 16)
            
            # 转换命令 (16进制字符串 -> int -> bytes)
            cmd_text = self.cmd_input.text().lower()
            if cmd_text.startswith('0x'):
                cmd_text = cmd_text[2:]
            cmd = int(cmd_text, 16)
            
            # 获取负载数据
            payload_text = self.payload_input.text().strip()
            if payload_text:
                payload = bytes.fromhex(payload_text)
            else:
                payload = b''
            
            # 显示发送进度条动画
            self.animate_send_progress()
            
            # 构造数据包
            data_packet = self.xprotocol.build_packet(head, dtype, addr, cmd, payload)
            
            # 发送数据
            self.serial_port.write(data_packet)
            
            # 统计字节数
            self.bytes_sent += len(data_packet)
            
            # 显示数据流动画
            self.data_flow.set_direction("send")
            
            # 显示发送的数据
            hex_data = data_packet.hex(' ').upper()
            self.add_message_to_recv(f">>> 发送: {hex_data}")
            
            # 更新状态栏
            self.statusBar().showMessage(f"发送成功: {len(data_packet)}字节")
            
        except Exception as e:
            self.show_error(f"发送数据失败: {str(e)}")
    
    def animate_send_progress(self):
        """发送进度条动画"""
        self.send_progress.setValue(0)
        animation = QPropertyAnimation(self.send_progress, b"value")
        animation.setDuration(500)  # 500毫秒动画
        animation.setStartValue(0)
        animation.setEndValue(100)
        animation.setEasingCurve(QEasingCurve.OutCubic)
        animation.start()
    
    def animate_recv_progress(self, value):
        """接收进度条动画"""
        current = self.recv_progress.value()
        if current > value:
            self.recv_progress.setValue(0)
        
        animation = QPropertyAnimation(self.recv_progress, b"value")
        animation.setDuration(300)  # 300毫秒动画
        animation.setStartValue(current)
        animation.setEndValue(value)
        animation.setEasingCurve(QEasingCurve.OutCubic)
        animation.start()
    
    def handle_received_data(self, data):
        """处理接收到的数据"""
        try:
            self.rx_buffer.extend(data)
            
            # 显示数据流动画
            self.data_flow.set_direction("receive")
            
            # 统计接收字节数
            self.bytes_received += len(data)
            
            # 显示接收进度
            progress_value = min(100, int(len(data) / 2))
            self.animate_recv_progress(progress_value)
            
            # 尝试解析完整数据包
            if self.parse_protocol.isChecked():
                while True:
                    packet, consumed = self.xprotocol.parse_packet(self.rx_buffer)
                    if packet:
                        # 显示解析后的数据
                        self.display_parsed_packet(packet)
                        # 移除已处理的数据
                        self.rx_buffer = self.rx_buffer[consumed:]
                        # 统计数据包
                        self.packet_count += 1
                    else:
                        break
            
            # 直接显示原始数据
            if self.hex_display.isChecked():
                hex_data = data.hex(' ').upper()
                self.add_message_to_recv(f"<<< 接收: {hex_data}")
            else:
                try:
                    text_data = data.decode('utf-8')
                    self.add_message_to_recv(f"<<< 接收: {text_data}")
                except:
                    hex_data = data.hex(' ').upper()
                    self.add_message_to_recv(f"<<< 接收: {hex_data}")
                    
            # 更新状态栏
            self.statusBar().showMessage(f"接收: {len(data)}字节")
                    
        except Exception as e:
            self.show_error(f"处理接收数据错误: {str(e)}")
    
    def display_parsed_packet(self, packet):
        """显示解析后的数据包"""
        if not packet:
            return
        
        # 保存当前的文本颜色
        self.recv_text.setTextColor(QColor(CYBER_BLUE))
            
        self.recv_text.append("┌── 解析数据包 ───────────────")
        
        # 高亮显示帧头
        self.recv_text.append(f"│ 帧头: 0x{packet['head'].hex().upper()}")
        
        # 高亮显示正确的CRC
        crc_color = CYBER_GREEN if packet['crc_valid'] else CYBER_PINK
        
        self.recv_text.append(f"│ 长度: {packet['len']}")
        self.recv_text.append(f"│ 设备类型: 0x{packet['dtype']:04X}")
        self.recv_text.append(f"│ 地址: 0x{packet['addr']:04X}")
        self.recv_text.append(f"│ 命令: 0x{packet['cmd']:04X}")
        self.recv_text.append(f"│ 负载: {packet['payload'].hex(' ').upper()}")
        
        # 显示CRC校验结果
        self.recv_text.setTextColor(QColor(crc_color))
        crc_text = "有效" if packet['crc_valid'] else "无效"
        self.recv_text.append(f"│ CRC: 0x{packet['crc'].hex().upper()} ({crc_text})")
        
        # 恢复文本颜色
        self.recv_text.setTextColor(QColor(CYBER_BLUE))
        self.recv_text.append("└─────────────────────────────")
        
        # 恢复默认文本颜色
        self.recv_text.setTextColor(QColor(CYBER_GREEN))
        
        # 自动滚动
        if self.auto_scroll.isChecked():
            self.recv_text.moveCursor(QTextCursor.End)
        
        # 也添加到数据包分析选项卡
        self.add_to_packets_tab(packet)
    
    def add_to_packets_tab(self, packet):
        """添加数据包到分析选项卡"""
        # 根据过滤设置决定是否显示
        filter_type = self.filter_combo.currentText()
        if filter_type == "仅有效数据包" and not packet['crc_valid']:
            return
        if filter_type == "仅无效数据包" and packet['crc_valid']:
            return
        
        # 设置文本颜色
        if packet['crc_valid']:
            self.packets_text.setTextColor(QColor(CYBER_GREEN))
        else:
            self.packets_text.setTextColor(QColor(CYBER_PINK))
        
        # 添加时间戳和包信息
        timestamp = time.strftime("%H:%M:%S")
        self.packets_text.append(f"[{timestamp}] 包类型: 0x{packet['dtype']:04X}, 地址: 0x{packet['addr']:04X}, 命令: 0x{packet['cmd']:04X}")
        
        # 添加负载数据
        if packet['payload']:
            self.packets_text.append(f"  负载: {packet['payload'].hex(' ').upper()}")
        else:
            self.packets_text.append("  无负载数据")
        
        # 添加CRC信息
        crc_text = "有效" if packet['crc_valid'] else "无效"
        self.packets_text.append(f"  CRC: 0x{packet['crc'].hex().upper()} ({crc_text})")
        
        # 添加分隔线
        self.packets_text.append("-" * 50)
        
        # 自动滚动
        self.packets_text.moveCursor(QTextCursor.End)
    
    def save_data(self):
        """保存接收的数据"""
        try:
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            filename = f"RS485_Log_{timestamp}.txt"
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(self.recv_text.toPlainText())
                
            self.statusBar().showMessage(f"数据已保存至 {filename}")
            self.add_message_to_recv(f"数据已保存至 {filename}", "system")
            
        except Exception as e:
            self.show_error(f"保存数据失败: {str(e)}")
    
    def clear_send_area(self):
        """清空发送区域"""
        self.payload_input.clear()
    
    def clear_recv_area(self):
        """清空接收区域"""
        self.recv_text.clear()
        self.rx_buffer.clear()
        self.statusBar().showMessage("接收区已清空")
    
    def show_error(self, message):
        """显示错误信息"""
        self.add_message_to_recv(f"错误: {message}", "error")
        self.statusBar().showMessage(f"错误: {message}")
    
    def closeEvent(self, event):
        """窗口关闭事件"""
        if self.serial_port.isOpen():
            if self.serial_thread:
                self.serial_thread.stop()
            self.serial_port.close()
        event.accept()

    def select_firmware_file(self):
        """选择固件文件"""
        file_dialog = QFileDialog(self)
        file_dialog.setFileMode(QFileDialog.ExistingFile)
        file_dialog.setNameFilter("固件文件 (*.bin *.hex *.img);;所有文件 (*.*)")
        
        if file_dialog.exec_():
            selected_files = file_dialog.selectedFiles()
            if selected_files:
                filepath = selected_files[0]
                self.load_firmware_file(filepath)
    
    def load_firmware_file(self, filepath):
        """加载固件文件并计算MD5"""
        try:
            # 读取文件内容
            with open(filepath, 'rb') as f:
                self.firmware_data = f.read()
                
            # 更新文件路径
            self.firmware_path = filepath
            self.firmware_path_edit.setText(filepath)
            
            # 更新文件大小
            self.firmware_size = len(self.firmware_data)
            size_text = self.get_readable_size(self.firmware_size)
            self.firmware_size_label.setText(size_text)
            
            # 计算MD5
            md5_hash = hashlib.md5()
            md5_hash.update(self.firmware_data)
            self.firmware_md5 = md5_hash.hexdigest().upper()
            self.firmware_md5_label.setText(self.firmware_md5)
            
            # 计算包数
            packet_count = (self.firmware_size + FIRMWARE_PACKET_SIZE - 1) // FIRMWARE_PACKET_SIZE
            self.packet_count_label.setText(f"{packet_count} 包")
            
            # 重置进度条
            self.firmware_progress.setValue(0)
            self.firmware_progress.setMaximum(packet_count)
            
            # 添加状态信息
            self.add_firmware_status(f"已加载文件: {os.path.basename(filepath)}")
            self.add_firmware_status(f"文件大小: {size_text}, MD5: {self.firmware_md5}")
            self.add_firmware_status(f"分包数: {packet_count}包, 每包{FIRMWARE_PACKET_SIZE}字节")
            
            # 启用下载按钮
            self.download_btn.setEnabled(True)
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"加载固件文件失败: {str(e)}")
            self.firmware_data = None
            self.firmware_path = ""
            self.firmware_size = 0
            self.firmware_md5 = ""
    
    def get_readable_size(self, size_bytes):
        """将字节大小转换为可读形式"""
        if size_bytes < 1024:
            return f"{size_bytes} 字节"
        elif size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.2f} KB"
        else:
            return f"{size_bytes / (1024 * 1024):.2f} MB"
    
    def add_firmware_status(self, message):
        """添加固件下载状态信息"""
        timestamp = time.strftime("%H:%M:%S")
        self.firmware_status.append(f"[{timestamp}] {message}")
        # 滚动到底部
        self.firmware_status.moveCursor(QTextCursor.End)
    
    def start_firmware_download(self):
        """开始固件下载"""
        if not self.serial_port.isOpen():
            QMessageBox.warning(self, "警告", "请先打开串口")
            return
            
        if not self.firmware_data:
            QMessageBox.warning(self, "警告", "请先选择固件文件")
            return
        
        try:
            # 获取设备地址
            version_text = self.firmware_version_input.text().lower()
            if version_text.startswith('0x'):
                version_text = version_text[2:]
            device_version = int(version_text, 16)
            
            # 使用数据类型0作为固件下载的类型
            dtype_text=self.dtype_input.text().lower()
            if dtype_text.startswith('0x'):
                dtype_text = dtype_text[2:]
            firmware_dtype = int(dtype_text, 16)

            addr_text=self.addr_input.text().lower()
            if addr_text.startswith('0x'):
                addr_text = addr_text[2:]
            device_addr = int(addr_text, 16)

            
            # 创建并启动下载线程
            self.firmware_download_thread = FirmwareDownloadThread(
                self.serial_port, self.firmware_data, firmware_dtype, device_addr,device_version
            )
            
            # 连接信号
            self.firmware_download_thread.progress_signal.connect(self.update_firmware_progress)
            self.firmware_download_thread.complete_signal.connect(self.firmware_download_complete)
            self.firmware_download_thread.error_signal.connect(self.firmware_download_error)
            self.firmware_download_thread.status_signal.connect(self.add_firmware_status)
            self.firmware_download_thread.save_signal.connect(self.save_firmware_chunk)
            
            # 启动线程
            self.firmware_download_thread.start()
            
            # 更新UI
            self.download_btn.setEnabled(False)
            self.pause_btn.setEnabled(True)
            self.stop_btn.setEnabled(True)
            self.pause_btn.setText("暂停")
            
            # 清空接收的固件数据
            self.received_firmware = bytearray()
            
            # 添加状态信息
            self.add_firmware_status("开始下载固件...")
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"启动下载失败: {str(e)}")
    
    def update_firmware_progress(self, current, total):
        """更新固件下载进度"""
        self.firmware_progress.setMaximum(total)
        self.firmware_progress.setValue(current)
    
    def toggle_firmware_download(self):
        """暂停/继续固件下载"""
        if not self.firmware_download_thread:
            return
            
        if self.firmware_download_thread.paused:
            # 继续下载
            self.firmware_download_thread.resume()
            self.pause_btn.setText("暂停")
        else:
            # 暂停下载
            self.firmware_download_thread.pause()
            self.pause_btn.setText("继续")
    
    def stop_firmware_download(self):
        """停止固件下载"""
        if not self.firmware_download_thread:
            return
            
        # 停止下载线程
        self.firmware_download_thread.stop()
        self.firmware_download_thread = None
        
        # 更新UI
        self.download_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        
        # 添加状态信息
        self.add_firmware_status("下载已停止")
    
    def firmware_download_complete(self):
        """固件下载完成"""
        self.download_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        
        # 添加状态信息
        self.add_firmware_status("固件下载完成!")
        
        # 清理下载线程
        self.firmware_download_thread = None
    
    def firmware_download_error(self, error_message):
        """固件下载错误"""
        self.add_firmware_status(f"错误: {error_message}")
        
        # 重置UI
        self.download_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        
        # 清理下载线程
        self.firmware_download_thread = None
    
    def save_firmware_chunk(self, data):
        """保存接收到的固件数据块"""
        # 这里是保存接收到的数据块的逻辑
        # 在实际应用中，可能需要根据具体需求实现不同的保存方式
        self.received_firmware.extend(data)
        
        # 计算已接收数据的MD5
        md5_hash = hashlib.md5()
        md5_hash.update(self.received_firmware)
        received_md5 = md5_hash.hexdigest().upper()
        
        # 添加状态信息
        received_size = len(self.received_firmware)
        size_text = self.get_readable_size(received_size)
        self.add_firmware_status(f"已接收数据: {size_text}, MD5: {received_md5}")
        
        # 可选：将数据保存到临时文件
        # 此处仅为示例，实际应用中可能需要根据需求修改
        try:
            temp_filename = f"firmware_received_{time.strftime('%Y%m%d_%H%M%S')}.tmp"
            with open(temp_filename, 'wb') as f:
                f.write(self.received_firmware)
            self.add_firmware_status(f"数据已保存至临时文件: {temp_filename}")
        except Exception as e:
            self.add_firmware_status(f"保存数据出错: {str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = RS485Tool()
    window.show()
    sys.exit(app.exec_())